/**
 * @file qt5.cpp
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "qt5.h"
#if USE_QT5

#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include <QMainWindow>
#include "ui_mainwindow.h"
#include <QKeyEvent>
#include <QPainter>
#include <QTimer>

/*********************
 *      DEFINES
 *********************/
#ifndef KEYBOARD_BUFFER_SIZE
#define KEYBOARD_BUFFER_SIZE 64
#endif

#ifndef TICK_TIMER_SLICE
#define TICK_TIMER_SLICE 1
#endif

/**********************
 *      TYPEDEFS
 **********************/

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    virtual void keyPressEvent(QKeyEvent *evn);
    virtual void mousePressEvent(QMouseEvent *evn);
    virtual void mouseReleaseEvent(QMouseEvent *evn);
    virtual void mouseMoveEvent(QMouseEvent *evn);
    virtual void wheelEvent(QWheelEvent *evn);

    void paintEvent(QPaintEvent *evn);

private slots:
    void tick();
    void task();

private:
    Ui::MainWindow *ui;
    QTimer *lv_tick_timer, *lv_task_timer;
};

typedef struct {
    QMainWindow * window;
    volatile bool qt_refr_qry;
#if QT_DOUBLE_BUFFERED
    uint32_t * tft_fb_act;
#else
    uint32_t * tft_fb;
#endif
}monitor_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void window_create(monitor_t * m);
static void window_update(monitor_t * m);
static void monitor_qt_refr(lv_timer_t * t);

/***********************
 *   GLOBAL PROTOTYPES
 ***********************/

/**********************
 *  STATIC VARIABLES
 **********************/
monitor_t monitor;

#if QT_DUAL_DISPLAY
monitor_t monitor2;
#endif

static volatile bool qt_quit_qry = false;

static bool left_button_down = false;
static int16_t last_x = 0;
static int16_t last_y = 0;

static int16_t wheel_diff = 0;
static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED;

static char key_buf[KEYBOARD_BUFFER_SIZE];

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void qt5_init(void)
{
    /*Initialize the QT MainWindow*/
    window_create(&monitor);
}

/**
 * Flush a buffer to the marked area
 * @param disp_drv pointer to driver where this function belongs
 * @param area an area where to copy `color_p`
 * @param color_p an array of pixels to copy to the `area` part of the screen
 */
void qt5_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    lv_coord_t hres = disp_drv->hor_res;
    lv_coord_t vres = disp_drv->ver_res;

    /*Return if the area is out the screen*/
    if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) {
        lv_disp_flush_ready(disp_drv);
        return;
    }

    int32_t y;
#if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32    /*32 is valid but support 24 for backward compatibility too*/
    int32_t x;
    for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            monitor.tft_fb[y * disp_drv->hor_res + x] = lv_color_to16(*color_p);
            color_p++;
        }

    }
#else
    uint32_t w = lv_area_get_width(area);
    for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) {
        memcpy(&monitor.tft_fb[y * QT_HOR_RES + area->x1], color_p, w * sizeof(lv_color_t));
        color_p += w;
    }
#endif

    monitor.qt_refr_qry = true;

    /* TYPICALLY YOU DO NOT NEED THIS
     * If it was the last part to refresh update the texture of the window.*/
    if(lv_disp_flush_is_last(disp_drv)) {
        monitor_qt_refr(NULL);
    }

    /*IMPORTANT! It must be called to tell the system the flush is ready*/
    lv_disp_flush_ready(disp_drv);
}

#if SDL_DUAL_DISPLAY

/**
 * Flush a buffer to the marked area
 * @param disp_drv pointer to driver where this function belongs
 * @param area an area where to copy `color_p`
 * @param color_p an array of pixels to copy to the `area` part of the screen
 */
void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{

}
#endif

/**
 * Get the current position and state of the mouse
 * @param indev_drv pointer to the related input device driver
 * @param data store the mouse data here
 */
void qt5_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    (void) indev_drv;      /*Unused*/

    /*Store the collected data*/
    data->point.x = last_x;
    data->point.y = last_y;
    data->state = left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
}

/**
 * Get encoder (i.e. mouse wheel) ticks difference and pressed state
 * @param indev_drv pointer to the related input device driver
 * @param data store the read data here
 */
void qt5_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    (void) indev_drv;      /*Unused*/

    data->state = wheel_state;
    data->enc_diff = wheel_diff;
    wheel_diff = 0;
}

/**
 * Get input from the keyboard.
 * @param indev_drv pointer to the related input device driver
 * @param data store the red data here
 */
void qt5_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    (void) indev_drv;      /*Unused*/

    static bool dummy_read = false;
    const size_t len = strlen(key_buf);

    /*Send a release manually*/
    if (dummy_read) {
        dummy_read = false;
        data->state = LV_INDEV_STATE_RELEASED;
        data->continue_reading = len > 0;
    }
    /*Send the pressed character*/
    else if (len > 0) {
        dummy_read = true;
        data->state = LV_INDEV_STATE_PRESSED;
        data->key = key_buf[0];
        memmove(key_buf, key_buf + 1, len);
        data->continue_reading = true;
    }
}


/**********************
 *   STATIC FUNCTIONS
 **********************/

static void monitor_qt_refr(lv_timer_t * t)
{
    (void)t;

    /*Refresh handling*/
    if(monitor.qt_refr_qry != false) {
        monitor.qt_refr_qry = false;
        window_update(&monitor);
    }

#if QT_DUAL_DISPLAY
    if(monitor2.qt_refr_qry != false) {
        monitor2.qt_refr_qry = false;
        window_update(&monitor2);
    }
#endif
}

static void window_create(monitor_t * m)
{
    int flag = 0;
#if QT_FULLSCREEN
#endif

    m->window = new MainWindow;
    m->window->setFixedSize(QT_HOR_RES * QT_ZOOM, QT_VER_RES * QT_ZOOM + 45);
    m->window->show();

    /*Initialize the frame buffer to gray (77 is an empirical value) */
#if QT_DOUBLE_BUFFERED
#else
    m->tft_fb = (uint32_t *)malloc(sizeof(uint32_t) * QT_HOR_RES * QT_VER_RES);
    memset(m->tft_fb, 0x44, QT_HOR_RES * QT_VER_RES * sizeof(uint32_t));
#endif

    m->qt_refr_qry = true;
}

static void window_update(monitor_t * m)
{
#if QT_DOUBLE_BUFFERED == 0
#else
#endif
#if LV_COLOR_SCREEN_TRANSP
#endif

    /*Update the qt window*/
    m->window->update();
}

/**********************
 *   MainWindow
 **********************/

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->setMouseTracking(true);
    this->centralWidget()->setMouseTracking(true);
#if LV_TICK_CUSTOM == 0
    lv_tick_timer = new QTimer;
    connect(lv_tick_timer, &QTimer::timeout, this, &MainWindow::tick);
    lv_tick_timer->start(TICK_TIMER_SLICE);
#endif
    lv_task_timer = new QTimer;
    connect(lv_task_timer, &QTimer::timeout, this, &MainWindow::task);
    lv_task_timer->start(10);
}

MainWindow::~MainWindow()
{
    delete ui;
#if LV_TICK_CUSTOM == 0
    delete lv_tick_timer;
#endif
    delete lv_task_timer;
}

/**
 * Convert a QT key code to it's LV_KEY_* counterpart or return '\0' if it's not a control character.
 * @param sdl_key the key code
 * @return LV_KEY_* control character or '\0'
 */
static uint32_t keycode_to_ctrl_key(int keycode)
{
    /*Remap some key to LV_KEY_... to manage groups*/
    switch(keycode) {
        case Qt::Key_Right :
        case Qt::Key_Plus:
            return LV_KEY_RIGHT;

        case Qt::Key_Left:
        case Qt::Key_Minus:
            return LV_KEY_LEFT;

        case Qt::Key_Up:
            return LV_KEY_UP;

        case Qt::Key_Down:
            return LV_KEY_DOWN;

        case Qt::Key_Escape:
            return LV_KEY_ESC;

        case Qt::Key_Backspace:
            return LV_KEY_BACKSPACE;

        case Qt::Key_Delete:
            return LV_KEY_DEL;

        case Qt::Key_Enter:
        case '\r':
            return LV_KEY_ENTER;

        case Qt::Key_Tab:
        case Qt::Key_PageDown:
            return LV_KEY_NEXT;

        case Qt::Key_PageUp:
            return LV_KEY_PREV;

        default:
            break;
    }
    return keycode;
}

void MainWindow::keyPressEvent(QKeyEvent *evn)
{
    int key = evn->key();

    if (key >= 0x20 && key <= 0x7F) {
        const size_t len = strlen(key_buf) + 1;
        if (len < KEYBOARD_BUFFER_SIZE - 1) {
            key_buf[len-1] = key;
            key_buf[len] = 0;
        }
    } else {
        const uint32_t ctrl_key = keycode_to_ctrl_key(key);
        if (ctrl_key == '\0')
            return;
        const size_t len = strlen(key_buf);
        if (len < KEYBOARD_BUFFER_SIZE - 1) {
            key_buf[len] = ctrl_key;
            key_buf[len + 1] = '\0';
        }
    }
}

void MainWindow::mousePressEvent(QMouseEvent *evn)
{
    if (evn->button() == Qt::MiddleButton) {
        wheel_state = LV_INDEV_STATE_PRESSED;
    } else if (evn->button() == Qt::LeftButton) {
        left_button_down = true;
        last_x = evn->pos().x() / QT_ZOOM;
        last_y = evn->pos().y() / QT_ZOOM;
    }
}

void MainWindow::mouseReleaseEvent(QMouseEvent *evn)
{
    if (evn->button() == Qt::MiddleButton) {
        wheel_state = LV_INDEV_STATE_RELEASED;
    } else if (evn->button() == Qt::LeftButton) {
        left_button_down = false;
    }
}

void MainWindow::mouseMoveEvent(QMouseEvent *evn)
{
    last_x = evn->pos().x() / QT_ZOOM;
    last_y = evn->pos().y() / QT_ZOOM;
}

void MainWindow::wheelEvent(QWheelEvent *evn)
{
    wheel_diff = -evn->delta();
}

void MainWindow::paintEvent(QPaintEvent *evn)
{
    Q_UNUSED(evn);
    QPainter painter(this);
    QImage img((const uchar *)monitor.tft_fb, QT_HOR_RES, QT_VER_RES, QImage::Format_ARGB32);

    img = img.scaled(QT_HOR_RES * QT_ZOOM, QT_VER_RES * QT_ZOOM);
    painter.drawImage(0, 0, img);
}

void MainWindow::tick()
{
#if LV_TICK_CUSTOM == 0
    lv_tick_inc(TICK_TIMER_SLICE); /*Tell LittelvGL that 5 milliseconds were elapsed*/
#endif
}

void MainWindow::task()
{
    lv_task_handler();
}

#include "qt5.moc"

#endif /* USE_QT5 */
